Skip to content

fix(input): keep the keyboard dismissed after a drag that starts on the input - #1166

Open
maksg wants to merge 2 commits into
mainfrom
rn-input-drag-refocus
Open

maksg wants to merge 2 commits into
mainfrom
rn-input-drag-refocus

Conversation

@maksg

@maksg maksg commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator

What/Why?

Fixes #1158.

On iOS, an EnrichedMarkdownTextInput inside a ScrollView with keyboardDismissMode="on-drag" focused itself again after a short drag that started on it. The keyboard closed and came straight back.

This is a regression from #884, which gave the input React Native's press handling:

  • The input keeps the JS responder during a scroll, because rejectResponderTermination defaults to true.
  • A short drag ends inside the press area, so the release counts as a press.
  • A press focuses the input.

The input now tracks how far the touch moves from where it went down. After more than 10 points, the press no longer focuses the input, also when the finger comes back to where it started. onPress still fires. A drag that starts on an unfocused input no longer focuses it either.

Notes for review:

  • React Native's TextInput uses the same press config and re-focuses in the same case (checked with a multiline TextInput on RN 0.88). This PR is a deliberate difference from it.
  • The drag state resets in onResponderGrant, not in onPressIn. Pressability calls onPressIn again when the finger re-enters the press area, so a reset there would treat "drag out and come back" as a new tap.
  • Android is not affected according to the issue: there the native scroll cancels the touch in JS.
  • The second commit adds a Regression category to the input stories, with a story that reproduces the issue.

Testing

  • New jest test, __tests__/input-press-focus.test.tsx. It covers taps, short drags, a drag that returns to its start, and a drag that leaves the press area and comes back. Four of its eight cases fail on main.

  • By hand in the example app: Storybook → EnrichedMarkdownTextInput → Regression → "Keyboard Dismiss On Drag ([iOS] Input re-focuses after a short drag in a ScrollView with keyboardDismissMode="on-drag" #1158)".

    1. Tap the enriched input.
    2. Drag a short way, starting on the input, and release.
    3. The keyboard must stay closed and the status line must read focus 1 · blur 1. On main it reads focus 2 · blur 1 and the keyboard comes back.

    The story also has a React Native TextInput, which still re-focuses, for comparison.

  • Run on the iOS 26.0 simulator on an earlier revision of the fix. The final revision is covered by the jest test. Not run on Android.

Screen recordings

Before After
ScreenRecording_11-12-2026.11-00-29_1.MP4
ScreenRecording_11-12-2026.11-01-14_1.MP4

PR Checklist

  • Code compiles and runs on iOS
  • Code compiles and runs on Android
  • Updated documentation/README if applicable
  • Ran example app to verify changes
  • E2E tests are passing
  • Required E2E tests have been added (if applicable)

maksg and others added 2 commits November 12, 2026 11:22
…he input

On iOS, an input inside a ScrollView with keyboardDismissMode="on-drag"
focused itself again after a short drag that started on it. The drag
dismissed the keyboard, but the input kept the JS responder. The release
then counted as a press, and a press focuses the input.

The input now tracks how far the touch moves from where it went down.
After more than 10 points the press no longer focuses the input, also
when the finger comes back to where it started. onPress still fires.
A drag on an unfocused input no longer focuses it either.

React Native's TextInput focuses itself in the same case, so this is a
deliberate difference from it.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…r a drag

The story puts the enriched input and React Native's TextInput in a
ScrollView with keyboardDismissMode="on-drag" and shows a focus and blur
count for each. A short drag that starts on the enriched input must
leave its focus count unchanged.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>

@jnowakow jnowakow left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The re-focus comes from the JS focusTextInput call in onPress, not from UIKit. The threshold adds a second tap-vs-drag check on top of UIKit's, and the two can still disagree, since 10 pt isn't UIKit's threshold.

An alternative: let the native view be the only thing that decides for touches inside the input, and keep the JS focus for the hitSlop area, which is what it was added for.

  • JS: in onResponderGrant, note whether the touch went down outside the view's bounds (size from onLayout), and only then call focusTextInput in onPress.
  • iOS: RCTViewComponentView sizes the content view to the frame minus padding, so taps on the padding never reach the UITextView. This sends them there, like TextInput, which keeps padding in textContainerInset:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
  UIView *hitView = [super hitTest:point withEvent:event];
  if (hitView == self && CGRectContainsPoint(self.bounds, point)) {
    return _textView;
  }
  return hitView;
}

I tried it on the iOS simulator with your regression story: a short drag on the text or the padding leaves the keyboard dismissed, and taps on both still focus. Trade-offs: it's a native change, and a drag that starts in the hitSlop area still re-focuses.

What do you think?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[iOS] Input re-focuses after a short drag in a ScrollView with keyboardDismissMode="on-drag"

2 participants